/*
 * $Id: rwl.c,v 1.3 2005-12-28 14:46:55 bacon Exp $
 */

#include <xp/bas/rwl.h>
#include <xp/bas/memory.h>
#include <xp/bas/errno.h>

/* TODO: more strict error handling */

xp_rwl_t* xp_rwl_open (xp_rwl_t* rwl)
{
	if (rwl == XP_NULL) {
		rwl = (xp_rwl_t*)xp_malloc (xp_sizeof(xp_rwl_t));
		if (rwl == XP_NULL) return XP_NULL;
		rwl->__dynamic = xp_true;
	}
	else rwl->__dynamic = xp_false;

	if (xp_mtx_open(&rwl->mtx) == XP_NULL) {
		int num = xp_geterrno();

		if (rwl->__dynamic) xp_free (rwl);

		xp_seterrno (num);
		return XP_NULL;
	}

	if (xp_cond_open(&rwl->rcond) == XP_NULL) {
		int num = xp_geterrno();

		xp_mtx_close (&rwl->mtx);
		if (rwl->__dynamic) xp_free (rwl);

		xp_seterrno (num);
		return XP_NULL;
	}

	if (xp_cond_open (&rwl->wcond) == XP_NULL) {
		int num = xp_geterrno();

		xp_cond_close (&rwl->rcond);
		xp_mtx_close (&rwl->mtx);
		if (rwl->__dynamic) xp_free (rwl);

		xp_seterrno (num);
		return XP_NULL;
	}

	rwl->rwait_count = 0;
	rwl->wwait_count = 0;
	rwl->ractive_count = 0;
	rwl->wactive_count = 0;

	return rwl;
}

void xp_rwl_close (xp_rwl_t* rwl)
{
	xp_cond_close (&rwl->wcond);
	xp_cond_close (&rwl->rcond);
	xp_mtx_close (&rwl->mtx);
	if (rwl->__dynamic) xp_free (rwl);
}

int xp_rwl_lockr (xp_rwl_t* rwl)
{
	if (xp_mtx_lock(&rwl->mtx) == -1) return -1;

	if (rwl->wactive_count > 0) {
		rwl->rwait_count++;
		while (rwl->wactive_count > 0) {
			xp_cond_wait (&rwl->rcond, &rwl->mtx);
		}
		rwl->rwait_count--;
	}

	rwl->ractive_count++;
	xp_mtx_unlock (&rwl->mtx);
	return 0;
}

int xp_rwl_trylockr (xp_rwl_t* rwl)
{
	if (xp_mtx_lock(&rwl->mtx) == -1) return -1;

	if (rwl->wactive_count > 0) {
		xp_mtx_unlock (&rwl->mtx);
		xp_seterrno (XP_EBUSY);
		return -1;
	}

	rwl->ractive_count++;
	xp_mtx_unlock (&rwl->mtx);
	return 0;
}

int xp_rwl_unlockr (xp_rwl_t* rwl)
{
	if (xp_mtx_lock(&rwl->mtx) == -1) return -1;

	if (rwl->ractive_count <= 0) {
		xp_mtx_unlock (&rwl->mtx);
		xp_seterrno (XP_EINVAL);
		return -1;
	}

	rwl->ractive_count--;
	if (rwl->ractive_count == 0 && rwl->wwait_count > 0) {
		xp_cond_signal (&rwl->wcond);
	}

	xp_mtx_unlock (&rwl->mtx);
	return 0;
}

int xp_rwl_lockw (xp_rwl_t* rwl)
{
	if (xp_mtx_lock(&rwl->mtx) == -1) return -1;

	if (rwl->wactive_count > 0 || rwl->ractive_count > 0) {
		rwl->wwait_count++;
		while (rwl->wactive_count > 0 || rwl->ractive_count > 0) {
			xp_cond_wait (&rwl->wcond, &rwl->mtx);
		}
		rwl->wwait_count--;
	}

	rwl->wactive_count++;
	xp_mtx_unlock (&rwl->mtx);
	return 0;
}

int xp_rwl_trylockw (xp_rwl_t* rwl)
{
	if (xp_mtx_lock(&rwl->mtx) == -1) return -1;

	if (rwl->wactive_count > 0 || rwl->ractive_count > 0) {
		xp_mtx_unlock (&rwl->mtx);
		xp_seterrno (XP_EBUSY);
		return -1;
	}

	rwl->wactive_count++;
	xp_mtx_unlock (&rwl->mtx);
	return 0;
}

int xp_rwl_unlockw (xp_rwl_t* rwl)
{
	if (xp_mtx_lock(&rwl->mtx) == -1) return -1;

	if (rwl->wactive_count <= 0) {
		xp_mtx_unlock (&rwl->mtx);
		xp_seterrno (XP_EINVAL);
		return -1;
	}

	rwl->wactive_count--;
	if (rwl->rwait_count > 0) {
		xp_cond_broadcast (&rwl->rcond);
	}
	else if (rwl->wwait_count > 0) {
		xp_cond_signal (&rwl->wcond);
	}

	xp_mtx_unlock (&rwl->mtx);
	return 0;
}
